#include "esp_log.h"
#include "api_config.h"
#include "mqtt_client.h"
#include "wifi.h"
#include "config.h"
#include "mqtt.h"
#include "api_map.h"

#define MQTT_CONFIG_ITEM "mqtt"

static const char *TAG = "MQTT";

#define DEVICE_NAME "esp_bat_pb"
#define DEVICE_MANUF "espressif"
#define DEVICE_MODEL chip_name

extern ha_entity_t _mqtt_entity_start;
extern ha_entity_t _mqtt_entity_end;
static const ha_entity_t entity_end = (ha_entity_t)&_mqtt_entity_end;

#pragma pack(1)
typedef struct mqtt_config
{
    char ip[24];
    uint16_t port;
    char user[33];
    char pwd[33];
    char key[33];
    uint8_t have_user : 1;
    uint8_t have_pwd : 1;
    uint8_t have_key : 1;
} mqtt_config_t;
#pragma pack()

static const char *const entity_type_to_str[ENTITY_MAX] = {
    "sensor",
    "switch",
    "button",
    "text",
    "tag",
    "device_tracker",
};

static char device_id[] = "esp_bat_pb_00112233";
static bool mqtt_config_need_save = false;
static bool mqtt_client_connect = false;
static esp_mqtt_client_handle_t mqtt_client = NULL;

static mqtt_config_t mqtt_config = {
    .ip = "mqtt://",
    .port = 1883,
    // .user = "esp-bat-pb",
    // .pwd = "esp-bat-pb888",
    // .key = "9cc3f43ecd6244278e93e2e94fe9db17",
    // .have_user = 1,
    // .have_pwd = 1,
    // .have_pwd = 1,
};

// 发布 discovery 配置
static void publish_discovery_config(void)
{
    char topic[128];
    char payload[512];

    for (ha_entity_t e = (ha_entity_t)&_mqtt_entity_start; e < entity_end; e++)
    {
        uint16_t len = 0;
        ha_entity_s entity;
        memcpy(&entity, e, sizeof(ha_entity_s));
        if (entity.type >= ENTITY_MAX)
            continue;

        len += snprintf(payload + len, sizeof(payload) - len,
                        "{"
                        "\"name\":\"%s\","
                        "\"uniq_id\":\"%s_%s\","
                        "\"device\":{"
                        "\"identifiers\":[\"%s\"],"
                        "\"name\":\"%s\","
                        "\"model\":\"%s\","
                        "\"manufacturer\":\"%s\""
                        "},",
                        entity.name,
                        DEVICE_NAME, entity.name,
                        device_id,
                        device_id,
                        DEVICE_MODEL,
                        DEVICE_MANUF);

        snprintf(topic, sizeof(topic), "homeassistant/%s/%s/%s/config", entity_type_to_str[entity.type], device_id, entity.name);

        if (entity.set_state)
        {
            if (entity.get_state)
                len += snprintf(payload + len, sizeof(payload) - len,
                                "\"command_topic\":\"%s/%s/%s/set\","
                                "\"state_topic\":\"%s/%s/%s/state\","
                                "\"icon\":\"mdi:power\"",
                                device_id, entity_type_to_str[entity.type], entity.name,
                                device_id, entity_type_to_str[entity.type], entity.name);
            else
                len += snprintf(payload + len, sizeof(payload) - len,
                                "\"command_topic\":\"%s/%s/%s\","
                                "\"icon\":\"mdi:power\"",
                                device_id, entity_type_to_str[entity.type], entity.name);
        }
        else if (entity.type == ENTITY_TAG)
            len += snprintf(payload + len, sizeof(payload) - len,
                            "\"topic\":\"%s/%s/%s\"",
                            device_id, entity_type_to_str[entity.type], entity.name);
        else
            len += snprintf(payload + len, sizeof(payload) - len,
                            "\"state_topic\":\"%s/%s/%s\"",
                            device_id, entity_type_to_str[entity.type], entity.name);

        /* 不设置设备类型, 只设置图标 */
        if (strstr(entity.name, "voltage") || strstr(entity.name, "bat_diff"))
            len += snprintf(payload + len, sizeof(payload) - len, ",\"icon\":\"mdi:alpha-v-circle-outline\"");
        else if (strstr(entity.name, "mah") || strstr(entity.name, "mwh"))
            len += snprintf(payload + len, sizeof(payload) - len, ",\"icon\":\"mdi:car-battery\"");
        else if (strstr(entity.name, "alert"))
            len += snprintf(payload + len, sizeof(payload) - len, ",\"icon\":\"mdi:alert\"");
        else if (strstr(entity.name, "current"))
            len += snprintf(payload + len, sizeof(payload) - len, ",\"icon\":\"mdi:current-dc\"");
        else if (strstr(entity.name, "power"))
            len += snprintf(payload + len, sizeof(payload) - len, ",\"icon\":\"mdi:flash\"");
        else if (strstr(entity.name, "wifi"))
            len += snprintf(payload + len, sizeof(payload) - len, ",\"icon\":\"mdi:wifi\"");
        else if (strstr(entity.name, "reset") || strstr(entity.name, "restart"))
            len += snprintf(payload + len, sizeof(payload) - len, ",\"icon\":\"mdi:restart\"");

        if (entity.type == ENTITY_SENSOR)
        {
            const char *device_class = NULL;
            if (strstr(entity.name, "temp"))
                device_class = "temperature";
            else if (strstr(entity.name, "time"))
                device_class = "duration";
            else if (strstr(entity.name, "soc") || strstr(entity.name, "soh"))
                device_class = "battery";

            len += snprintf(payload + len, sizeof(payload) - len,
                            ",\"value_template\": \"{{ value | round(3) }}\"");

            len += snprintf(payload + len, sizeof(payload) - len,
                            ",\"state_class\":\"measurement\"");

            if (entity.unit)
                len += snprintf(payload + len, sizeof(payload) - len, ",\"unit_of_measurement\":\"%s\"", entity.unit);
            if (device_class)
                len += snprintf(payload + len, sizeof(payload) - len, ",\"device_class\":\"%s\"", device_class);
        }
        len += snprintf(payload + len, sizeof(payload) - len, "}");

        esp_mqtt_client_publish(mqtt_client, topic, payload, len, 0, 0);
        ESP_LOGI(TAG, "Published discovery for %s: %s", topic, payload);

        // 订阅开关命令
        if (entity.set_state)
        {
            if (entity.get_state)
            {
                snprintf(topic, sizeof(topic), "%s/%s/%s/set", device_id, entity_type_to_str[entity.type], entity.name);
                esp_mqtt_client_subscribe(mqtt_client, topic, 0);

                snprintf(topic, sizeof(topic), "%s/%s/%s/state", device_id, entity_type_to_str[entity.type], entity.name);
                len = entity.get_state(entity.user_data, entity.id, payload, sizeof(payload));
                esp_mqtt_client_publish(mqtt_client, topic, payload, len, 0, 0);
            }
            else
            {
                snprintf(topic, sizeof(topic), "%s/%s/%s", device_id, entity_type_to_str[entity.type], entity.name);
                esp_mqtt_client_subscribe(mqtt_client, topic, 0);
            }
        }
    }
}

void report_sensor_state(void)
{
    if (!mqtt_client_connect)
        return;

    char topic[128];
    char buf[64];

    for (ha_entity_t e = (ha_entity_t)&_mqtt_entity_start; e < entity_end; e++)
    {
        ha_entity_s entity;
        memcpy(&entity, e, sizeof(ha_entity_s));
        if (entity.get_state)
        {
            uint8_t len = entity.get_state(entity.user_data, entity.id, buf, sizeof(buf));
            if (len)
            {
                if (entity.set_state)
                    snprintf(topic, sizeof(topic), "%s/%s/%s/state", device_id, entity_type_to_str[entity.type], entity.name);
                else
                    snprintf(topic, sizeof(topic), "%s/%s/%s", device_id, entity_type_to_str[entity.type], entity.name);

                if (esp_mqtt_client_publish(mqtt_client, topic, buf, len, 0, 0) == -1)
                    return;
                // ESP_LOGI(TAG, "Report %s=%s", topic, buf);
            }
        }
    }
}

// MQTT 回调
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    esp_mqtt_event_handle_t event = event_data;
    switch ((esp_mqtt_event_id_t)event_id)
    {
    case MQTT_EVENT_DISCONNECTED:
    {
        mqtt_client_connect = false;
        break;
    }
    case MQTT_EVENT_CONNECTED:
    {
        ESP_LOGI(TAG, "Connected to MQTT Broker");
        publish_discovery_config();
        mqtt_client_connect = true;

        if (mqtt_config_need_save)
        {
            mqtt_config_need_save = false;
            if (config_set(MQTT_CONFIG_ITEM, sizeof(mqtt_config), (uint8_t *)&mqtt_config))
                config_commit();
        }
        break;
    }

    case MQTT_EVENT_DATA:
    {
        printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", event->data_len, event->data);

        char topic_str[128];
        snprintf(topic_str, sizeof(topic_str), "%.*s", event->topic_len, event->topic);

        char data_str[64];
        snprintf(data_str, sizeof(data_str), "%.*s", event->data_len, event->data);

        for (ha_entity_t e = (ha_entity_t)&_mqtt_entity_start; e < entity_end; e++)
        {
            ha_entity_s entity;
            memcpy(&entity, e, sizeof(ha_entity_s));
            if (entity.set_state)
            {
                if (entity.type >= ENTITY_MAX)
                    continue;

                char expected_topic[128];

                if (entity.get_state)
                    snprintf(expected_topic, sizeof(expected_topic), "%s/%s/%s/set", device_id, entity_type_to_str[entity.type], entity.name);
                else
                    snprintf(expected_topic, sizeof(expected_topic), "%s/%s/%s", device_id, entity_type_to_str[entity.type], entity.name);

                if (strcmp(topic_str, expected_topic) == 0)
                {
                    entity.set_state(entity.user_data, entity.id, data_str);

                    // 上报最新状态
                    if (entity.get_state)
                    {
                        char buf[64];
                        snprintf(topic_str, sizeof(topic_str), "%s/%s/%s/state", device_id, entity_type_to_str[entity.type], entity.name);
                        uint8_t len = entity.get_state(entity.user_data, entity.id, buf, sizeof(buf));
                        esp_mqtt_client_publish(mqtt_client, topic_str, buf, len, 0, 0);
                        ESP_LOGI(TAG, "%s %s -> %s", entity_type_to_str[entity.type], topic_str, buf);
                    }
                    break;
                }
            }
        }
        break;
    }
    default:
        break;
    }
}

void mqtt_server_stop(void)
{
    ESP_LOGI(TAG, "MQTT STOP");
    mqtt_client_connect = false;
    if (mqtt_client)
    {
        esp_mqtt_client_disconnect(mqtt_client);
        esp_mqtt_client_stop(mqtt_client);
        if (mqtt_config_need_save)
        {
            esp_mqtt_client_destroy(mqtt_client);
            mqtt_client = NULL;
        }
    }
}
static void mqtt_restart(void)
{
    mqtt_server_stop();

    if (mqtt_config.ip[7] == '\0')
        return;

    ESP_LOGI(TAG, "MQTT START");
    if (!mqtt_client)
    {
        esp_mqtt_client_config_t mqtt_cfg = {
            .broker.address.uri = mqtt_config.ip,
            .broker.address.port = mqtt_config.port,
            .credentials.client_id = device_id,
            .credentials.username = mqtt_config.have_user ? mqtt_config.user : NULL,
            .credentials.authentication.password = mqtt_config.have_pwd ? mqtt_config.pwd : NULL,
            .credentials.authentication.key = mqtt_config.have_key ? mqtt_config.key : NULL,
            .outbox.limit = 1024,
        };

        mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
        esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    }

    esp_mqtt_client_start(mqtt_client);
    esp_mqtt_client_reconnect(mqtt_client);
}

void mqtt_server_start(void)
{
    mqtt_restart();
}

static void set_mqtt(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    if (!data || !cJSON_IsObject(data))
        goto set_mqtt_error;
    /**
        {
            "ip": "xxx.xxx.xxx.xxx",
            "port": xxxx,
            "user":"xxxx",
            "pwd":"xxxx",
            "key":"xxxx"
        }
    */
    static const char *const obj_keys[] = {
        "ip",
        "port",
        "user",
        "pwd",
        "key",
    };
#define obj_keys_len sizeof(obj_keys) / sizeof(obj_keys[0])
    cJSON *obj_list[obj_keys_len] = {0};
    for (uint8_t i = 0; i < obj_keys_len; i++)
    {
        if (!cJSON_HasObjectItem(data, obj_keys[i]))
            goto set_mqtt_error;
        obj_list[i] = cJSON_GetObjectItem(data, obj_keys[i]);
    }

    mqtt_config_t cfg = mqtt_config;
    char *str = cJSON_GetStringValue(obj_list[0]);

    if (str && strlen(str) > 0)
        strncpy(&cfg.ip[7], str, sizeof(cfg.ip) - 7);
    else
        memset(&cfg.ip[7], 0, sizeof(cfg.ip) - 7);

    cfg.port = cJSON_GetNumberValue(obj_list[1]);

    str = cJSON_GetStringValue(obj_list[2]);
    cfg.have_user = str && strlen(str) > 0;
    if (cfg.have_user)
        strncpy(cfg.user, str, sizeof(cfg.user));
    else
        memset(cfg.user, 0, sizeof(cfg.user));

    str = cJSON_GetStringValue(obj_list[3]);
    cfg.have_pwd = str && strlen(str) > 0;
    if (cfg.have_pwd)
        strncpy(cfg.pwd, str, sizeof(cfg.pwd));
    else
        memset(cfg.pwd, 0, sizeof(cfg.pwd));

    str = cJSON_GetStringValue(obj_list[4]);
    cfg.have_key = str && strlen(str) > 0;
    if (cfg.have_key)
        strncpy(cfg.key, str, sizeof(cfg.key));
    else
        memset(cfg.key, 0, sizeof(cfg.key));

    if (!memcmp(&cfg, &mqtt_config, sizeof(mqtt_config_t)))
        return ws_reply_data(server, frame, "set_mqtt", "No change");

    mqtt_config = cfg;
    mqtt_config_need_save = true;

    ESP_LOG_BUFFER_HEXDUMP(TAG, &cfg, sizeof(cfg), ESP_LOG_INFO);

    mqtt_restart();
    return ws_reply_status(server, frame, "set_mqtt", 0);

set_mqtt_error:
    return ws_reply_data(server, frame, "set_mqtt", "Incorrect/incomplete parameters");
}

static void get_mqtt(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    uint16_t len = 0;
    char *str = (char *)server->data;
    len += sprintf(&str[len], "{");
    len += sprintf(&str[len], "\"api\":\"get_mqtt\",");
    len += sprintf(&str[len], "\"data\":{");
    len += sprintf(&str[len], "\"ip\":\"%s\",", &mqtt_config.ip[7]);
    len += sprintf(&str[len], "\"port\":%d,", mqtt_config.port);
    len += sprintf(&str[len], "\"user\":\"%s\",", mqtt_config.user);
    len += sprintf(&str[len], "\"pwd\":\"%s\",", mqtt_config.pwd);
    len += sprintf(&str[len], "\"key\":\"%s\",", mqtt_config.key);
    len += sprintf(&str[len], "\"connect\":%d", mqtt_client_connect);
    len += sprintf(&str[len], "}");
    len += sprintf(&str[len], "}");
    frame->payload = server->data;
    frame->len = len;
}
static void mqtt_api_init(void)
{
    uint8_t mac[6] = {0};
    esp_base_mac_addr_get(mac);
    snprintf(device_id, 32, "esp_bat_pb_%02x%02x%02x%02x", mac[2], mac[3], mac[4], mac[5]);

    uint16_t len = config_get(MQTT_CONFIG_ITEM, NULL, 0);
    if (len)
        config_get(MQTT_CONFIG_ITEM, (uint8_t *)&mqtt_config, len);

    ADD_API_ITEM(get_mqtt);
    ADD_API_ITEM(set_mqtt);
}
ADD_API(mqtt_api_init)